package cn.coder.toolkit;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SignatureException;
import io.jsonwebtoken.security.WeakKeyException;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;

import javax.crypto.SecretKey;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * <a href="https://github.com/jwtk/jjwt">官网</a>
 * <dependency>
 *     <groupId>io.jsonwebtoken</groupId>
 *     <artifactId>jjwt</artifactId>
 *     <version>0.12.5</version>
 * </dependency>
 */
@Slf4j
public final class JwtKit {

    private static final String SECRET = "abcdefghijklmnopqrstuvwxyz123456"; // 自定义密钥
    private static final SecretKey KEY = Keys.hmacShaKeyFor(SECRET.getBytes()); // https://github.com/jwtk/jjwt#signed-jwts, 强制要求长度至少256bit即32byte, 纯字母数字的话需要32位

    public static void main(String[] args) {
        @Getter
        @Setter
        @ToString()
        @Accessors(chain = true)
        class Person {
            private String name;
        }
        @Getter
        @Setter
        @Accessors(chain = true)
        @ToString(callSuper = true)
        class User extends Person {
            private String username;
            private Integer age;
        }
        try {
            String token = generate(Duration.ofSeconds(2), new User().setUsername("coder").setAge(18).setName("name"));
            verify(token);
            System.out.println(verifyToMap(token));
            System.out.println(verifyToObj(token, User.class));
        } catch (Throwable cause) {
            cause.printStackTrace();
        }
    }

    /**
     * 生成令牌
     * @param duration 有效期
     * @param map 自定义数据
     */
    public static String generate(Duration duration, Map<String, Object> map) {
        return Jwts.builder()
                .claims(map) // 自定义数据
                .issuedAt(new Date()) // 签发时间
                .expiration(DateTimeKit.Convert.LocalDateTime.toDate(LocalDateTime.now().plusSeconds(duration.getSeconds()))) // 过期时间
                .signWith(KEY) // since: 0.10.0, 会验证密钥长度, HmacSha 的最短 256 bit, 即 32 byte, 即纯字母数字得 32位长度
                .compact();
    }

    /**
     * 生成令牌
     * @param duration 有效期
     * @param object 自定义对象
     */
    public static String generate(Duration duration, Object object) {
        return generate(duration, objectToMap(object));
    }

    /**
     * 生成令牌
     * @param duration 有效期
     */
    public static String generate(Duration duration) {
        return generate(duration, null);
    }

    /**
     * 令牌校验
     */
    public static void verify(String token) {
        try {
            Jwts.parser().verifyWith(KEY).build().parse(token);
        } catch (WeakKeyException e) {
            log.info("弱密钥");
            throw e;
        } catch (SignatureException e) {
            log.info("加签验签的密钥不匹配");
            throw e;
        } catch (ExpiredJwtException e) {
            log.info("已过期");
            throw e;
        }
    }

    /**
     * 令牌校验
     */
    public static Map<String, Object> verifyToMap(String token) {
        try {
            Jws<Claims> jws = Jwts.parser()
                    .verifyWith(KEY)
                    .build()
                    .parseSignedClaims(token);
            return jws.getPayload();
        } catch (WeakKeyException e) {
            log.info("弱密钥");
            throw e;
        } catch (SignatureException e) {
            log.info("加签验签的密钥不匹配");
            throw e;
        } catch (ExpiredJwtException e) {
            log.info("已过期");
            throw e;
        }
    }

    /**
     * 令牌校验
     */
    public static <T> T verifyToObj(String token, Class<T> clazz) {
        try {
            Jws<Claims> jws = Jwts.parser()
                    .verifyWith(KEY)
                    .build()
                    .parseSignedClaims(token);
            Claims payload = jws.getPayload();
            return mapToObject(payload, clazz);
        } catch (WeakKeyException e) {
            log.info("弱密钥");
            throw e;
        } catch (SignatureException e) {
            log.info("加签验签的密钥不匹配");
            throw e;
        } catch (ExpiredJwtException e) {
            log.info("已过期");
            throw e;
        }
    }

    private static Map<String, Object> objectToMap(Object object) {
        try {
            if (null == object) {
                return null;
            }
            Map<String, Object> map = new LinkedHashMap<>();
            Class<?> clazz = object.getClass();
            do {
                Field[] array = clazz.getDeclaredFields();
                for (Field field : array) {
                    field.setAccessible(true);
                    if (Modifier.isStatic(field.getModifiers())) {
                        continue;
                    }
                    Object value = field.get(object);
                    map.put(field.getName(), value);
                }
                clazz = clazz.getSuperclass();
            } while (Object.class != clazz);
            return map;
        } catch (Throwable cause) {
            throw new RuntimeException(cause);
        }
    }

    private static <T> T mapToObject(Map<String, Object> map, Class<? super T> clazz) {
        try {
            if (null == map) {
                return null;
            }
            Constructor<? super T> constructor = clazz.getDeclaredConstructor();
            constructor.setAccessible(true);
            Object object = constructor.newInstance();
            do {
                Field[] array = clazz.getDeclaredFields();
                for (Field field : array) {
                    if (Modifier.isStatic(field.getModifiers())) {
                        continue;
                    }
                    field.setAccessible(true);
                    field.set(object, map.get(field.getName()));
                }
                clazz = clazz.getSuperclass();
            } while (Object.class != clazz);
            return (T) object;
        } catch (Throwable cause) {
            throw new RuntimeException(cause);
        }
    }

}
